import {
  TransactionMeta,
  TransactionStatus,
  TransactionType,
} from '@metamask/transaction-controller';
import { renderHook } from '@testing-library/react-hooks';
import Engine from '../../../../core/Engine';
import { useMusdConversionStatus } from './useMusdConversionStatus';
import useEarnToasts, { EarnToastOptionsConfig } from './useEarnToasts';
import { ToastVariants } from '../../../../component-library/components/Toast/Toast.types';
import { IconName } from '../../../../component-library/components/Icons/Icon';
import { NotificationFeedbackType } from 'expo-haptics';

// Mock all external dependencies
jest.mock('../../../../core/Engine');
jest.mock('./useEarnToasts');
jest.mock('../../Bridge/hooks/useAssetMetadata/utils', () => ({
  getAssetImageUrl: jest.fn(),
}));
jest.mock('react-redux', () => ({
  useSelector: jest.fn(),
}));
jest.mock('../../../../selectors/tokenListController', () => ({
  selectERC20TokensByChain: jest.fn(),
}));
jest.mock('../../../../selectors/transactionPayController', () => ({
  selectTransactionPayTransactionData: jest.fn(),
}));

import { useSelector } from 'react-redux';
import { getAssetImageUrl } from '../../Bridge/hooks/useAssetMetadata/utils';
import { selectERC20TokensByChain } from '../../../../selectors/tokenListController';
import { selectTransactionPayTransactionData } from '../../../../selectors/transactionPayController';

const mockUseSelector = jest.mocked(useSelector);
const mockGetAssetImageUrl = jest.mocked(getAssetImageUrl);
const mockSelectERC20TokensByChain = jest.mocked(selectERC20TokensByChain);
const mockSelectTransactionPayTransactionData = jest.mocked(
  selectTransactionPayTransactionData,
);

type TransactionStatusUpdatedHandler = (event: {
  transactionMeta: TransactionMeta;
}) => void;

const mockSubscribe = jest.fn<
  void,
  [string, TransactionStatusUpdatedHandler]
>();
const mockUnsubscribe = jest.fn<
  void,
  [string, TransactionStatusUpdatedHandler]
>();
const mockUseEarnToasts = jest.mocked(useEarnToasts);

Object.defineProperty(Engine, 'controllerMessenger', {
  value: {
    subscribe: mockSubscribe,
    unsubscribe: mockUnsubscribe,
  },
  writable: true,
  configurable: true,
});

describe('useMusdConversionStatus', () => {
  const mockShowToast = jest.fn();
  const mockInProgressToast = {
    variant: ToastVariants.Icon as const,
    iconName: IconName.Loading,
    hasNoTimeout: true,
    iconColor: '#000000',
    backgroundColor: '#FFFFFF',
    hapticsType: NotificationFeedbackType.Warning,
    labelOptions: [{ label: 'In Progress', isBold: true }],
  };
  const mockInProgressFn = jest.fn(() => mockInProgressToast);
  const mockEarnToastOptions: EarnToastOptionsConfig = {
    mUsdConversion: {
      inProgress: mockInProgressFn,
      success: {
        variant: ToastVariants.Icon as const,
        iconName: IconName.CheckBold,
        hasNoTimeout: false,
        iconColor: '#000000',
        backgroundColor: '#FFFFFF',
        hapticsType: NotificationFeedbackType.Success,
        labelOptions: [{ label: 'Success', isBold: true }],
      },
      failed: {
        variant: ToastVariants.Icon as const,
        iconName: IconName.Danger,
        hasNoTimeout: false,
        iconColor: '#000000',
        backgroundColor: '#FFFFFF',
        hapticsType: NotificationFeedbackType.Error,
        labelOptions: [{ label: 'Failed', isBold: true }],
      },
    },
  };

  // Default mock data
  const defaultTokensChainsCache = {};
  const defaultTransactionPayData = {};

  beforeEach(() => {
    jest.clearAllMocks();
    jest.useFakeTimers();
    mockInProgressFn.mockClear();

    mockUseEarnToasts.mockReturnValue({
      showToast: mockShowToast,
      EarnToastOptions: mockEarnToastOptions,
    });

    // Setup useSelector to return different values based on selector
    mockUseSelector.mockImplementation((selector) => {
      if (selector === mockSelectERC20TokensByChain) {
        return defaultTokensChainsCache;
      }
      if (selector === mockSelectTransactionPayTransactionData) {
        return defaultTransactionPayData;
      }
      return {};
    });

    mockGetAssetImageUrl.mockReturnValue('https://example.com/token-icon.png');
  });

  // Helper to setup token cache mock
  const setupTokensCacheMock = (tokenData: Record<string, unknown>) => {
    mockUseSelector.mockImplementation((selector) => {
      if (selector === mockSelectERC20TokensByChain) {
        return tokenData;
      }
      if (selector === mockSelectTransactionPayTransactionData) {
        return defaultTransactionPayData;
      }
      return {};
    });
  };

  // Helper to setup transaction pay data mock
  const setupTransactionPayDataMock = (
    transactionPayData: Record<string, unknown>,
    tokenData: Record<string, unknown> = {},
  ) => {
    mockUseSelector.mockImplementation((selector) => {
      if (selector === mockSelectERC20TokensByChain) {
        return tokenData;
      }
      if (selector === mockSelectTransactionPayTransactionData) {
        return transactionPayData;
      }
      return {};
    });
  };

  afterEach(() => {
    jest.clearAllMocks();
    jest.useRealTimers();
  });

  const createTransactionMeta = (
    status: TransactionStatus,
    transactionId = 'test-transaction-1',
    type = TransactionType.musdConversion,
    metamaskPay?: { chainId?: string; tokenAddress?: string },
  ): TransactionMeta =>
    ({
      id: transactionId,
      status,
      type,
      chainId: '0x1',
      networkClientId: 'mainnet',
      time: Date.now(),
      txParams: {
        from: '0x123',
        to: '0x456',
      },
      ...(metamaskPay && { metamaskPay }),
    }) as TransactionMeta;

  const getSubscribedHandler = (): TransactionStatusUpdatedHandler => {
    const subscribeCalls = mockSubscribe.mock.calls;
    const lastCall = subscribeCalls.at(-1);
    if (!lastCall) {
      throw new Error('No subscription found');
    }
    return lastCall[1];
  };

  describe('subscription lifecycle', () => {
    it('subscribes to TransactionController:transactionStatusUpdated on mount', () => {
      renderHook(() => useMusdConversionStatus());

      expect(mockSubscribe).toHaveBeenCalledTimes(1);
      expect(mockSubscribe).toHaveBeenCalledWith(
        'TransactionController:transactionStatusUpdated',
        expect.any(Function),
      );
    });

    it('unsubscribes from TransactionController:transactionStatusUpdated on unmount', () => {
      const { unmount } = renderHook(() => useMusdConversionStatus());

      const handler = getSubscribedHandler();

      unmount();

      expect(mockUnsubscribe).toHaveBeenCalledTimes(1);
      expect(mockUnsubscribe).toHaveBeenCalledWith(
        'TransactionController:transactionStatusUpdated',
        handler,
      );
    });
  });

  describe('approved transaction status', () => {
    it('shows in-progress toast when transaction status is approved', () => {
      renderHook(() => useMusdConversionStatus());

      const handler = getSubscribedHandler();
      const transactionMeta = createTransactionMeta(TransactionStatus.approved);

      handler({ transactionMeta });

      expect(mockShowToast).toHaveBeenCalledTimes(1);
      expect(mockShowToast).toHaveBeenCalledWith(mockInProgressToast);
    });

    it('prevents duplicate in-progress toast for same transaction', () => {
      renderHook(() => useMusdConversionStatus());

      const handler = getSubscribedHandler();
      const transactionMeta = createTransactionMeta(TransactionStatus.approved);

      handler({ transactionMeta });
      handler({ transactionMeta });
      handler({ transactionMeta });

      expect(mockShowToast).toHaveBeenCalledTimes(1);
    });

    it('passes token symbol and icon from metamaskPay data to in-progress toast', () => {
      const tokenAddress = '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48';
      const chainId = '0x89';
      const mockTokenData = {
        [chainId]: {
          data: {
            [tokenAddress]: { symbol: 'USDC' },
          },
        },
      };
      setupTokensCacheMock(mockTokenData);

      renderHook(() => useMusdConversionStatus());

      const handler = getSubscribedHandler();
      const transactionMeta = createTransactionMeta(
        TransactionStatus.approved,
        'test-tx-with-token',
        TransactionType.musdConversion,
        { chainId, tokenAddress },
      );

      handler({ transactionMeta });

      expect(mockGetAssetImageUrl).toHaveBeenCalledWith(
        tokenAddress.toLowerCase(),
        chainId,
      );
      expect(mockInProgressFn).toHaveBeenCalledWith({
        tokenSymbol: 'USDC',
        tokenIcon: 'https://example.com/token-icon.png',
        estimatedTimeSeconds: 15,
      });
    });

    it('uses lowercase token address as fallback for symbol lookup', () => {
      const tokenAddress = '0x6B175474E89094C44Da98b954EedeAC495271d0F';
      const chainId = '0x1';
      const mockTokenData = {
        [chainId]: {
          data: {
            [tokenAddress.toLowerCase()]: { symbol: 'DAI' },
          },
        },
      };
      setupTokensCacheMock(mockTokenData);

      renderHook(() => useMusdConversionStatus());

      const handler = getSubscribedHandler();
      const transactionMeta = createTransactionMeta(
        TransactionStatus.approved,
        'test-tx-lowercase',
        TransactionType.musdConversion,
        { chainId, tokenAddress },
      );

      handler({ transactionMeta });

      expect(mockInProgressFn).toHaveBeenCalledWith(
        expect.objectContaining({ tokenSymbol: 'DAI' }),
      );
    });

    it('uses "Token" as fallback when token symbol is not found', () => {
      const tokenAddress = '0x1111111111111111111111111111111111111111';
      const chainId = '0x1';
      setupTokensCacheMock({
        [chainId]: { data: {} },
      });

      renderHook(() => useMusdConversionStatus());

      const handler = getSubscribedHandler();
      const transactionMeta = createTransactionMeta(
        TransactionStatus.approved,
        'test-tx-unknown',
        TransactionType.musdConversion,
        { chainId, tokenAddress },
      );

      handler({ transactionMeta });

      expect(mockInProgressFn).toHaveBeenCalledWith(
        expect.objectContaining({ tokenSymbol: 'Token' }),
      );
    });

    it('passes empty tokenSymbol and undefined tokenIcon when payTokenAddress is missing', () => {
      renderHook(() => useMusdConversionStatus());

      const handler = getSubscribedHandler();
      const transactionMeta = createTransactionMeta(
        TransactionStatus.approved,
        'test-tx-no-token',
        TransactionType.musdConversion,
        { chainId: '0x1' },
      );

      handler({ transactionMeta });

      expect(mockGetAssetImageUrl).not.toHaveBeenCalled();
      expect(mockInProgressFn).toHaveBeenCalledWith({
        tokenSymbol: 'Token',
        tokenIcon: undefined,
        estimatedTimeSeconds: 15,
      });
    });

    it('passes empty tokenSymbol and undefined tokenIcon when metamaskPay is missing', () => {
      renderHook(() => useMusdConversionStatus());

      const handler = getSubscribedHandler();
      const transactionMeta = createTransactionMeta(TransactionStatus.approved);

      handler({ transactionMeta });

      expect(mockGetAssetImageUrl).not.toHaveBeenCalled();
      expect(mockInProgressFn).toHaveBeenCalledWith({
        tokenSymbol: 'Token',
        tokenIcon: undefined,
        estimatedTimeSeconds: 15,
      });
    });

    it('uses estimatedDuration from transaction pay data when available', () => {
      const transactionId = 'test-tx-with-duration';
      setupTransactionPayDataMock({
        [transactionId]: {
          totals: {
            estimatedDuration: 45,
          },
        },
      });

      renderHook(() => useMusdConversionStatus());

      const handler = getSubscribedHandler();
      const transactionMeta = createTransactionMeta(
        TransactionStatus.approved,
        transactionId,
      );

      handler({ transactionMeta });

      expect(mockInProgressFn).toHaveBeenCalledWith(
        expect.objectContaining({ estimatedTimeSeconds: 45 }),
      );
    });

    it('falls back to default estimated time when transaction pay data is missing', () => {
      setupTransactionPayDataMock({});

      renderHook(() => useMusdConversionStatus());

      const handler = getSubscribedHandler();
      const transactionMeta = createTransactionMeta(
        TransactionStatus.approved,
        'test-tx-no-pay-data',
      );

      handler({ transactionMeta });

      expect(mockInProgressFn).toHaveBeenCalledWith(
        expect.objectContaining({ estimatedTimeSeconds: 15 }),
      );
    });

    it('falls back to default estimated time when totals is missing', () => {
      const transactionId = 'test-tx-no-totals';
      setupTransactionPayDataMock({
        [transactionId]: {},
      });

      renderHook(() => useMusdConversionStatus());

      const handler = getSubscribedHandler();
      const transactionMeta = createTransactionMeta(
        TransactionStatus.approved,
        transactionId,
      );

      handler({ transactionMeta });

      expect(mockInProgressFn).toHaveBeenCalledWith(
        expect.objectContaining({ estimatedTimeSeconds: 15 }),
      );
    });

    it('falls back to default estimated time when estimatedDuration is missing', () => {
      const transactionId = 'test-tx-no-duration';
      setupTransactionPayDataMock({
        [transactionId]: {
          totals: {},
        },
      });

      renderHook(() => useMusdConversionStatus());

      const handler = getSubscribedHandler();
      const transactionMeta = createTransactionMeta(
        TransactionStatus.approved,
        transactionId,
      );

      handler({ transactionMeta });

      expect(mockInProgressFn).toHaveBeenCalledWith(
        expect.objectContaining({ estimatedTimeSeconds: 15 }),
      );
    });

    it('uses iconUrl from token cache when available', () => {
      const tokenAddress = '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48';
      const chainId = '0x89';
      const cachedIconUrl = 'https://cached.example.com/usdc-icon.png';
      const mockTokenData = {
        [chainId]: {
          data: {
            [tokenAddress]: {
              symbol: 'USDC',
              iconUrl: cachedIconUrl,
            },
          },
        },
      };
      setupTokensCacheMock(mockTokenData);

      renderHook(() => useMusdConversionStatus());

      const handler = getSubscribedHandler();
      const transactionMeta = createTransactionMeta(
        TransactionStatus.approved,
        'test-tx-cached-icon',
        TransactionType.musdConversion,
        { chainId, tokenAddress },
      );

      handler({ transactionMeta });

      expect(mockGetAssetImageUrl).not.toHaveBeenCalled();
      expect(mockInProgressFn).toHaveBeenCalledWith({
        tokenSymbol: 'USDC',
        tokenIcon: cachedIconUrl,
        estimatedTimeSeconds: 15,
      });
    });

    it('falls back to getAssetImageUrl when iconUrl is not in token cache', () => {
      const tokenAddress = '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48';
      const chainId = '0x89';
      const mockTokenData = {
        [chainId]: {
          data: {
            [tokenAddress]: {
              symbol: 'USDC',
              // No iconUrl
            },
          },
        },
      };
      setupTokensCacheMock(mockTokenData);

      renderHook(() => useMusdConversionStatus());

      const handler = getSubscribedHandler();
      const transactionMeta = createTransactionMeta(
        TransactionStatus.approved,
        'test-tx-fallback-icon',
        TransactionType.musdConversion,
        { chainId, tokenAddress },
      );

      handler({ transactionMeta });

      expect(mockGetAssetImageUrl).toHaveBeenCalledWith(
        tokenAddress.toLowerCase(),
        chainId,
      );
      expect(mockInProgressFn).toHaveBeenCalledWith({
        tokenSymbol: 'USDC',
        tokenIcon: 'https://example.com/token-icon.png',
        estimatedTimeSeconds: 15,
      });
    });

    it('uses both cached iconUrl and estimatedDuration together', () => {
      const tokenAddress = '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48';
      const chainId = '0x89';
      const transactionId = 'test-tx-full-data';
      const cachedIconUrl = 'https://cached.example.com/usdc-icon.png';

      const mockTokenData = {
        [chainId]: {
          data: {
            [tokenAddress]: {
              symbol: 'USDC',
              iconUrl: cachedIconUrl,
            },
          },
        },
      };

      const mockPayData = {
        [transactionId]: {
          totals: {
            estimatedDuration: 120,
          },
        },
      };

      setupTransactionPayDataMock(mockPayData, mockTokenData);

      renderHook(() => useMusdConversionStatus());

      const handler = getSubscribedHandler();
      const transactionMeta = createTransactionMeta(
        TransactionStatus.approved,
        transactionId,
        TransactionType.musdConversion,
        { chainId, tokenAddress },
      );

      handler({ transactionMeta });

      expect(mockGetAssetImageUrl).not.toHaveBeenCalled();
      expect(mockInProgressFn).toHaveBeenCalledWith({
        tokenSymbol: 'USDC',
        tokenIcon: cachedIconUrl,
        estimatedTimeSeconds: 120,
      });
    });
  });

  describe('confirmed transaction status', () => {
    it('shows success toast when transaction status is confirmed', () => {
      renderHook(() => useMusdConversionStatus());

      const handler = getSubscribedHandler();
      const transactionMeta = createTransactionMeta(
        TransactionStatus.confirmed,
      );

      handler({ transactionMeta });

      expect(mockShowToast).toHaveBeenCalledTimes(1);
      expect(mockShowToast).toHaveBeenCalledWith(
        mockEarnToastOptions.mUsdConversion.success,
      );
    });

    it('prevents duplicate success toast for same transaction', () => {
      renderHook(() => useMusdConversionStatus());

      const handler = getSubscribedHandler();
      const transactionMeta = createTransactionMeta(
        TransactionStatus.confirmed,
      );

      handler({ transactionMeta });
      handler({ transactionMeta });

      expect(mockShowToast).toHaveBeenCalledTimes(1);
    });

    it('cleans up toast tracking entries after 5 seconds for confirmed status', () => {
      renderHook(() => useMusdConversionStatus());

      const handler = getSubscribedHandler();
      const transactionId = 'test-transaction-1';
      const approvedMeta = createTransactionMeta(
        TransactionStatus.approved,
        transactionId,
      );
      const confirmedMeta = createTransactionMeta(
        TransactionStatus.confirmed,
        transactionId,
      );

      handler({ transactionMeta: approvedMeta });
      handler({ transactionMeta: confirmedMeta });

      expect(mockShowToast).toHaveBeenCalledTimes(2);

      jest.advanceTimersByTime(5000);

      // After cleanup, should be able to show toasts again for same transaction
      handler({ transactionMeta: approvedMeta });
      handler({ transactionMeta: confirmedMeta });

      expect(mockShowToast).toHaveBeenCalledTimes(4);
    });
  });

  describe('failed transaction status', () => {
    it('shows failed toast when transaction status is failed', () => {
      renderHook(() => useMusdConversionStatus());

      const handler = getSubscribedHandler();
      const transactionMeta = createTransactionMeta(TransactionStatus.failed);

      handler({ transactionMeta });

      expect(mockShowToast).toHaveBeenCalledTimes(1);
      expect(mockShowToast).toHaveBeenCalledWith(
        mockEarnToastOptions.mUsdConversion.failed,
      );
    });

    it('prevents duplicate failed toast for same transaction', () => {
      renderHook(() => useMusdConversionStatus());

      const handler = getSubscribedHandler();
      const transactionMeta = createTransactionMeta(TransactionStatus.failed);

      handler({ transactionMeta });
      handler({ transactionMeta });

      expect(mockShowToast).toHaveBeenCalledTimes(1);
    });

    it('cleans up toast tracking entries after 5 seconds for failed status', () => {
      renderHook(() => useMusdConversionStatus());

      const handler = getSubscribedHandler();
      const transactionId = 'test-transaction-2';
      const approvedMeta = createTransactionMeta(
        TransactionStatus.approved,
        transactionId,
      );
      const failedMeta = createTransactionMeta(
        TransactionStatus.failed,
        transactionId,
      );

      handler({ transactionMeta: approvedMeta });
      handler({ transactionMeta: failedMeta });

      expect(mockShowToast).toHaveBeenCalledTimes(2);

      jest.advanceTimersByTime(5000);

      // After cleanup, should be able to show toasts again for same transaction
      handler({ transactionMeta: approvedMeta });
      handler({ transactionMeta: failedMeta });

      expect(mockShowToast).toHaveBeenCalledTimes(4);
    });
  });

  describe('transaction flow from approved to final status', () => {
    it('shows both in-progress and success toasts for transaction flow', () => {
      renderHook(() => useMusdConversionStatus());

      const handler = getSubscribedHandler();
      const transactionId = 'test-transaction-3';
      const approvedMeta = createTransactionMeta(
        TransactionStatus.approved,
        transactionId,
      );
      const confirmedMeta = createTransactionMeta(
        TransactionStatus.confirmed,
        transactionId,
      );

      handler({ transactionMeta: approvedMeta });

      expect(mockShowToast).toHaveBeenCalledTimes(1);
      expect(mockShowToast).toHaveBeenCalledWith(mockInProgressToast);

      handler({ transactionMeta: confirmedMeta });

      expect(mockShowToast).toHaveBeenCalledTimes(2);
      expect(mockShowToast).toHaveBeenCalledWith(
        mockEarnToastOptions.mUsdConversion.success,
      );
    });

    it('shows both in-progress and failed toasts for transaction flow', () => {
      renderHook(() => useMusdConversionStatus());

      const handler = getSubscribedHandler();
      const transactionId = 'test-transaction-4';
      const approvedMeta = createTransactionMeta(
        TransactionStatus.approved,
        transactionId,
      );
      const failedMeta = createTransactionMeta(
        TransactionStatus.failed,
        transactionId,
      );

      handler({ transactionMeta: approvedMeta });

      expect(mockShowToast).toHaveBeenCalledTimes(1);
      expect(mockShowToast).toHaveBeenCalledWith(mockInProgressToast);

      handler({ transactionMeta: failedMeta });

      expect(mockShowToast).toHaveBeenCalledTimes(2);
      expect(mockShowToast).toHaveBeenCalledWith(
        mockEarnToastOptions.mUsdConversion.failed,
      );
    });
  });

  describe('non-mUSD conversion transactions', () => {
    it('ignores transaction when type is not mUSD conversion', () => {
      renderHook(() => useMusdConversionStatus());

      const handler = getSubscribedHandler();
      const transactionMeta = createTransactionMeta(
        TransactionStatus.approved,
        'test-transaction-5',
        'contractInteraction' as typeof TransactionType.musdConversion,
      );

      handler({ transactionMeta });

      expect(mockShowToast).not.toHaveBeenCalled();
    });

    it('ignores transaction when type is swap', () => {
      renderHook(() => useMusdConversionStatus());

      const handler = getSubscribedHandler();
      const transactionMeta = createTransactionMeta(
        TransactionStatus.confirmed,
        'test-transaction-6',
        'swap' as typeof TransactionType.musdConversion,
      );

      handler({ transactionMeta });

      expect(mockShowToast).not.toHaveBeenCalled();
    });

    it('ignores transaction when type is simpleSend', () => {
      renderHook(() => useMusdConversionStatus());

      const handler = getSubscribedHandler();
      const transactionMeta = createTransactionMeta(
        TransactionStatus.failed,
        'test-transaction-7',
        'simpleSend' as typeof TransactionType.musdConversion,
      );

      handler({ transactionMeta });

      expect(mockShowToast).not.toHaveBeenCalled();
    });
  });

  describe('other transaction statuses', () => {
    it('ignores transaction when status is unapproved', () => {
      renderHook(() => useMusdConversionStatus());

      const handler = getSubscribedHandler();
      const transactionMeta = createTransactionMeta(
        TransactionStatus.unapproved,
      );

      handler({ transactionMeta });

      expect(mockShowToast).not.toHaveBeenCalled();
    });

    it('ignores transaction when status is submitted', () => {
      renderHook(() => useMusdConversionStatus());

      const handler = getSubscribedHandler();
      const transactionMeta = createTransactionMeta(
        TransactionStatus.submitted,
      );

      handler({ transactionMeta });

      expect(mockShowToast).not.toHaveBeenCalled();
    });

    it('ignores transaction when status is signed', () => {
      renderHook(() => useMusdConversionStatus());

      const handler = getSubscribedHandler();
      const transactionMeta = createTransactionMeta(TransactionStatus.signed);

      handler({ transactionMeta });

      expect(mockShowToast).not.toHaveBeenCalled();
    });
  });

  describe('multiple concurrent transactions', () => {
    it('tracks and shows toasts for different transactions independently', () => {
      renderHook(() => useMusdConversionStatus());

      const handler = getSubscribedHandler();
      const transaction1Approved = createTransactionMeta(
        TransactionStatus.approved,
        'transaction-1',
      );
      const transaction2Approved = createTransactionMeta(
        TransactionStatus.approved,
        'transaction-2',
      );
      const transaction1Confirmed = createTransactionMeta(
        TransactionStatus.confirmed,
        'transaction-1',
      );
      const transaction2Failed = createTransactionMeta(
        TransactionStatus.failed,
        'transaction-2',
      );

      handler({ transactionMeta: transaction1Approved });
      handler({ transactionMeta: transaction2Approved });
      handler({ transactionMeta: transaction1Confirmed });
      handler({ transactionMeta: transaction2Failed });

      expect(mockShowToast).toHaveBeenCalledTimes(4);
      expect(mockShowToast).toHaveBeenNthCalledWith(1, mockInProgressToast);
      expect(mockShowToast).toHaveBeenNthCalledWith(2, mockInProgressToast);
      expect(mockShowToast).toHaveBeenNthCalledWith(
        3,
        mockEarnToastOptions.mUsdConversion.success,
      );
      expect(mockShowToast).toHaveBeenNthCalledWith(
        4,
        mockEarnToastOptions.mUsdConversion.failed,
      );
    });

    it('cleans up only entries for specific transaction after timeout', () => {
      renderHook(() => useMusdConversionStatus());

      const handler = getSubscribedHandler();
      const transaction1Confirmed = createTransactionMeta(
        TransactionStatus.confirmed,
        'transaction-1',
      );
      const transaction2Confirmed = createTransactionMeta(
        TransactionStatus.confirmed,
        'transaction-2',
      );

      handler({ transactionMeta: transaction1Confirmed });
      handler({ transactionMeta: transaction2Confirmed });

      expect(mockShowToast).toHaveBeenCalledTimes(2);

      jest.advanceTimersByTime(5000);

      // Both transactions should be cleaned up after 5 seconds
      handler({ transactionMeta: transaction1Confirmed });
      handler({ transactionMeta: transaction2Confirmed });

      expect(mockShowToast).toHaveBeenCalledTimes(4);
    });
  });

  describe('hook dependencies', () => {
    it('uses showToast function from useEarnToasts hook', () => {
      renderHook(() => useMusdConversionStatus());

      expect(mockUseEarnToasts).toHaveBeenCalledTimes(1);

      const handler = getSubscribedHandler();
      const transactionMeta = createTransactionMeta(TransactionStatus.approved);

      handler({ transactionMeta });

      expect(mockShowToast).toHaveBeenCalled();
    });

    it('uses EarnToastOptions from useEarnToasts hook', () => {
      renderHook(() => useMusdConversionStatus());

      const handler = getSubscribedHandler();
      const transactionMeta = createTransactionMeta(
        TransactionStatus.confirmed,
      );

      handler({ transactionMeta });

      expect(mockShowToast).toHaveBeenCalledWith(
        mockEarnToastOptions.mUsdConversion.success,
      );
    });
  });
});
